#include "dpdk_ring.h"

#include <rte_common.h>
#include <rte_log.h>
#include <rte_memory.h>
#include <rte_memzone.h>
#include <rte_launch.h>
#include <rte_eal.h>
#include <rte_per_lcore.h>
#include <rte_lcore.h>
#include <rte_debug.h>
#include <rte_atomic.h>
#include <rte_branch_prediction.h>
#include <rte_ring.h>
#include <rte_interrupts.h>
#include <rte_pci.h>
#include <rte_ether.h>
#include <rte_ethdev.h>
#include <rte_mempool.h>
#include <rte_memcpy.h>
#include <rte_mbuf.h>
#include <rte_string_fns.h>
#include <rte_cycles.h>

#define PKT_BURST 32
#define RTE_LOGTYPE_APP RTE_LOGTYPE_USER1

static struct rte_mempool* s_mp;

struct dpkd_cb_info
{
	int portid;
	dpdk_pkt_callback cb;
	void* arg;
};

/* Check the link status of all ports in up to 9s, and print them finally */
static void
check_port_link_status(uint8_t portid)
{
#define CHECK_INTERVAL 100 /* 100ms */
#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
	uint8_t count;
	struct rte_eth_link link;

	printf("\nChecking link status");
	fflush(stdout);
	for (count = 0; count <= MAX_CHECK_TIME; count++)
	{
		memset(&link, 0, sizeof(link));
		rte_eth_link_get_nowait(portid, &link);
		if (link.link_status)
		{
			printf("Port %d Link Up - speed %u "
			       "Mbps - %s\n", (uint8_t)portid,
			       (unsigned)link.link_speed,
			       (link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
			       ("full-duplex") : ("half-duplex\n"));
			return;
		}
		else
		{
			printf(".");
			fflush(stdout);
			rte_delay_ms(CHECK_INTERVAL);
		}
	}

	printf("Port %d Link Down\n", (uint8_t)portid);
}

static inline int
dpdk_port_init(uint8_t port, struct rte_mempool* mbuf_pool, uint16_t num_queues, int rx_ring_size, int tx_ring_size)
{
	struct rte_eth_conf port_conf =
	{
		.rxmode = {
			//.mq_mode	= ETH_MQ_RX_RSS,
			.split_hdr_size = 0,
			.header_split   = 0, /**< Header Split disabled */
			.hw_ip_checksum = 0, /**< IP checksum offload enabled */
			.hw_vlan_filter = 0, /**< VLAN filtering disabled */
			.jumbo_frame    = 0, /**< Jumbo Frame Support disabled */
			.hw_strip_crc   = 0, /**< CRC stripped by hardware */
			.max_rx_pkt_len = ETHER_MAX_LEN,
		},
		.txmode = {
			.mq_mode = ETH_MQ_TX_NONE,
		}
	};
	const uint16_t rx_rings = num_queues, tx_rings = num_queues;
	struct rte_eth_dev_info info;
	int retval;
	uint16_t q;

	if (rte_eal_process_type() == RTE_PROC_SECONDARY)
		return 0;

	if (port >= rte_eth_dev_count())
		return -1;

	printf("# Initialising port %u ...\n", (unsigned)port);

	rte_eth_dev_info_get(port, &info);
	//info.default_rxconf.rx_drop_en = 1;

	retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);
	if (retval < 0)
		return retval;

	for (q = 0; q < rx_rings; q ++)
	{
		retval = rte_eth_rx_queue_setup(port, q, rx_ring_size,
		                                rte_eth_dev_socket_id(port),
		                                &info.default_rxconf,
		                                mbuf_pool);
		if (retval < 0)
			return retval;
	}

	for (q = 0; q < tx_rings; q ++)
	{
		retval = rte_eth_tx_queue_setup(port, q, tx_ring_size,
		                                rte_eth_dev_socket_id(port),
		                                NULL);
		if (retval < 0)
			return retval;
	}

	rte_eth_promiscuous_enable(port);

	retval  = rte_eth_dev_start(port);
	if (retval < 0)
		return retval;

	return 0;
}


static int
lcore_main(void* arg)
{
	const unsigned id = rte_lcore_id();
	const uint16_t q_id = 0;
	unsigned i;
	uint8_t* pkt;
	int end_loop;
	//int rx_cnt = 0;
	struct dpkd_cb_info* info = (struct dpkd_cb_info*) arg;
	int port = info->portid;


	printf("lcore %u using queue %u of port %u\n", id, (unsigned)q_id, port);

	for (;;)
	{
		struct rte_mbuf* buf[PKT_BURST];

		const uint16_t rx_c = rte_eth_rx_burst(port, q_id, buf, PKT_BURST);
		if (rx_c == 0)
			continue;

		for (i = 0; i < rx_c; i++)
		{
			pkt = rte_pktmbuf_mtod(buf[i], void*);
			//printf("=[%d]= %4d bytes =====\n", ++rx_cnt, buf[i]->pkt_len);
			//display(pkt, rte_pktmbuf_pkt_len(buf[i]));
			end_loop = info->cb(pkt, buf[i]->pkt_len, info->arg);

			rte_pktmbuf_free(buf[i]);

			if (end_loop)
				goto end;
		}

	}

goto end:
	return 0;
}


#define NB_MBUFS 64*1024 /* use 64k mbufs */
#define MBUF_CACHE_SIZE 256

#define MAX_ARGV_LEN 64
#define MAX_ARGV_COUNT 10


int dpdk_init(int portid, dpdk_pkt_callback cb, void* arg, int nb_mbufs, int mbuf_cache_size, int rx_ring_size, int tx_ring_size)
{
	int ret;
	int rte_argc = 0;
	char rte_argvv[MAX_ARGV_COUNT][MAX_ARGV_LEN];
	char* rte_argv[MAX_ARGV_COUNT];

	static const char* _DPDK_RING_POOL = "DPDK_RING_POOL";

	enum rte_proc_type_t proc_type;

	int num_queues = 1;
	int channels = 1;
	int nb_ports, port_id;
	int cpu_mask = 1 << portid;

	snprintf(rte_argvv[rte_argc], MAX_ARGV_LEN, "dring");
	rte_argv[rte_argc] = rte_argvv[rte_argc];
	rte_argc++;
	snprintf(rte_argvv[rte_argc], MAX_ARGV_LEN, "-c%d", cpu_mask);
	rte_argv[rte_argc] = rte_argvv[rte_argc];
	rte_argc++;
	snprintf(rte_argvv[rte_argc], MAX_ARGV_LEN, "-n%d",  channels);
	rte_argv[rte_argc] = rte_argvv[rte_argc];
	rte_argc++;
	snprintf(rte_argvv[rte_argc], MAX_ARGV_LEN, "--proc-type=auto");
	rte_argv[rte_argc] = rte_argvv[rte_argc];
	rte_argc++;
	rte_argv[rte_argc] = NULL;

	ret = rte_eal_init(rte_argc, (char**)rte_argv);
	if (ret < 0)
		rte_exit(EXIT_FAILURE, "Cannot init EAL\n");

	/* determine the NIC devices available */
	nb_ports = rte_eth_dev_count();
	printf("dev count %d\n", nb_ports);
	if (nb_ports == 0)
		rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");

	proc_type = rte_eal_process_type();
	s_mp = (proc_type == RTE_PROC_SECONDARY) ?
	       rte_mempool_lookup(_DPDK_RING_POOL) :
	       rte_pktmbuf_pool_create(_DPDK_RING_POOL, nb_mbufs,
	                               mbuf_cache_size, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
	                               rte_socket_id());
	if (s_mp == NULL)
		rte_exit(EXIT_FAILURE, "Cannot get memory pool for buffers\n");

	/* Initialize all ports. */
	if (proc_type == RTE_PROC_PRIMARY)
	{
		for (port_id = 0; port_id < nb_ports; port_id++)
		{
			if (dpdk_port_init(port_id, s_mp, (uint16_t)num_queues, rx_ring_size, tx_ring_size) < 0)
				rte_exit(EXIT_FAILURE, "Error initialising ports\n");

			check_port_link_status((uint8_t)port_id);
		}
	}

	RTE_LOG(INFO, APP, "Finished Process Init.\n");

	struct dpkd_cb_info info =
	{
		.portid = portid,
		.cb = cb,
		.arg = arg,
	};

	rte_eal_mp_remote_launch(lcore_main, &info, CALL_MASTER);

	return 0;
}


int dpdk_send(int portid, uint8_t* pkt, int len)
{
	struct rte_mbuf* created_pkt;
	uint8_t* raw_pkt;
	int ret;

	created_pkt = rte_pktmbuf_alloc(s_mp);

	created_pkt->data_len = len;
	created_pkt->pkt_len = len;
	raw_pkt = rte_pktmbuf_mtod(created_pkt, uint8_t*);
	rte_memcpy(raw_pkt, pkt, len);

	/* Enable flag for hardware timestamping. */
	//created_pkt->ol_flags |= PKT_TX_IEEE1588_TMST;
	//created_pkt->ol_flags = 0;

	/* Transmit the packet. */
	ret = rte_eth_tx_burst(portid, 0, &created_pkt, 1);
	if (ret)
	{
		printf("send pkt done\n");
		return 0;
	}

	rte_pktmbuf_free(created_pkt);
	printf("send pkt failed!\n");
	return -1;
}




